package vg.services.user_interface_manager;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import javax.swing.*;

import vg.services.data_base_manager.interfaces.IModel;
import vg.services.main_manager.MainManager;
import vg.services.plugin_manager.event_and_request.event.AUIEvent;
import vg.services.plugin_manager.event_and_request.request.AUIRequest;
import vg.services.plugin_manager.event_and_request.request.EUIRequestType;
import vg.services.plugin_manager.event_and_request.request.IUIRequestOwner;
import vg.services.user_interface_manager.additional_swing_components.SimpleStatusBar;
import vg.services.user_interface_manager.interfaces.AMenuItem;
import vg.services.user_interface_manager.interfaces.AUserInterface;
import vg.services.user_interface_manager.interfaces.AUserInterfaceElement;
import vg.services.user_interface_manager.realization.Instruments;
import vg.services.user_interface_manager.realization.UserInterfacePanel;

/**
 * This class realizes user interface and use swing for it.
 * @author tzolotuhin
 */
public class UserInterfaceManager extends AUserInterface {
	// Components
	private JMenu fileMenu, editMenu, windowMenu, otherMenu, helpMenu, exportMenu, importMenu;
	private JMenuItem quitMenuItem;
	private JSplitPane splitFirst, splitSecond, splitThird;
	private JLabel progressName;
	// Main Components
	private final JFrame frame;
	private final JMenuBar menuBar;
	private final Instruments instruments;
	private final SimpleStatusBar statusBar;
	private final UserInterfacePanel navigatorPanel, desktopPanel, attributePanel, minimapPanel;
	private final Observer eventListener; // it need for UIEventChangeUIstyle and other
	//-------------------------------------------------------------------------
	private final static String DEF_WINDOWS_SIZE_X = "WindowSizeX";
	private final static String DEF_WINDOWS_SIZE_Y = "WindowSizeY";
	private final static String DEF_WINDOWS_POS_X = "WindowPosX";
	private final static String DEF_WINDOWS_POS_Y = "WindowPosY";
	private Integer windowSizeX, windowSizeY;
	private Integer windowPosX, windowPosY;
	//------------------------------------------------------------------------- 
	private WorkThread workThread;
	private JProgressBar progressBar;
	private final Timer progressTimer;
	//-------------------------------------------------------------------------
	public UserInterfaceManager(final IModel theModel, final String title) {
		super(theModel);
		//this.frameTitle = title;
		this.frame = new JFrame(title);
		// Creating of window
		this.loadWindowOptions();
		this.frame.setSize(this.windowSizeX, this.windowSizeY);
		this.frame.setLocation(this.windowPosX, this.windowPosY);
		this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		this.frame.setLayout(new BorderLayout());
		this.frame.addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent e) {
				super.componentResized(e);
			    Component c = (Component)e.getSource();
		        Integer sizeX = c.getSize().width;
		        Integer sizeY = c.getSize().height;
		        MainManager.config.setProperty(DEF_WINDOWS_SIZE_X, sizeX.toString());
		        MainManager.config.setProperty(DEF_WINDOWS_SIZE_Y, sizeY.toString());
			}
			public void componentMoved(ComponentEvent e) {
				super.componentMoved(e);
			    Component c = (Component)e.getSource();
		        Integer posX = c.getLocation().x;
		        Integer posY = c.getLocation().y;
		        MainManager.config.setProperty(DEF_WINDOWS_POS_X, posX.toString());
		        MainManager.config.setProperty(DEF_WINDOWS_POS_Y, posY.toString());
			}
		});
		this.frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				quit();
			}
		});
		// Creating of  menu
		this.menuBar = new JMenuBar();
		this.frame.setJMenuBar(menuBar);
		
		fileMenu  = new JMenu("File");
		editMenu  = new JMenu("Edit");
		windowMenu  = new JMenu("Window");
		otherMenu = new JMenu("Other");
		exportMenu = new JMenu("Export");
		importMenu = new JMenu("Import");
		helpMenu = new JMenu("Help");
		
		this.quitMenuItem  = new JMenuItem("Quit");
		
		this.quitMenuItem.setIcon(new ImageIcon("./data/resources/textures/quit.png"));
		// Creating components
		this.navigatorPanel = new UserInterfacePanel("Navigator", null);
		this.desktopPanel = new UserInterfacePanel("Desktop", null);
		this.attributePanel = new UserInterfacePanel("Attribute panel", null);
		this.minimapPanel = new UserInterfacePanel("Mini map", null);
		
		this.splitFirst = new JSplitPane(JSplitPane.VERTICAL_SPLIT, this.navigatorPanel.getView(), this.minimapPanel.getView());
		this.splitSecond = new JSplitPane(JSplitPane.VERTICAL_SPLIT, this.desktopPanel.getView(), this.attributePanel.getView());
		this.splitThird = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, this.splitFirst, this.splitSecond);
		
		this.splitFirst.setResizeWeight(0.7);
		this.splitFirst.setDividerSize(8);
		this.splitFirst.setOneTouchExpandable(true);
		this.splitSecond.setResizeWeight(0.7);
		this.splitSecond.setDividerSize(8);
		this.splitSecond.setOneTouchExpandable(true);
		this.splitThird.setResizeWeight(0.2);
		this.splitThird.setDividerSize(8);
		this.splitThird.setOneTouchExpandable(true);

		this.instruments = new Instruments();
		this.statusBar = new SimpleStatusBar();
		this.progressBar = new JProgressBar(0,100);
		this.progressBar.setMaximumSize(new Dimension(100,this.statusBar.getPreferredSize().height-2));
		this.progressName = new JLabel();
		this.statusBar.add(progressName);
		this.statusBar.add(progressBar);
		this.progressTimer = new Timer(500, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {	
						progressName.setText(MainManager.progressManager.getTaskName());
						progressBar.setValue((int) MainManager.progressManager.updateProgress());
						boolean visible = MainManager.progressManager.getTaskCount() > 0;
						progressName.setVisible(visible);
						progressBar.setVisible(visible);
					}
				});
			}
		});
		progressTimer.start();		
		this.workThread = new WorkThread();
		Thread t = new Thread(this.workThread);
		t.setDaemon(true);
		t.start();
		// Package UI
		this.frame.add(this.instruments.getView(),BorderLayout.NORTH);
		this.frame.add(this.splitThird,BorderLayout.CENTER);
		this.frame.add(this.statusBar,BorderLayout.SOUTH);
		
        // Adding of menu bar
		fileMenu.add(quitMenuItem);
		
		menuBar.add(fileMenu);
		menuBar.add(editMenu);
		menuBar.add(windowMenu);
		menuBar.add(helpMenu);
		// Adding mnemonic for menus
		fileMenu.setMnemonic('f');
		editMenu.setMnemonic('e');
		helpMenu.setMnemonic('h');	
		quitMenuItem.setMnemonic('q');
		quitMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				quit();
			}
		});
		quitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, KeyEvent.ALT_DOWN_MASK));
		// Add event and request listener for this window
		this.eventListener = new Observer() {
			public synchronized void update(Observable o, Object arg) {
				if(arg instanceof AUIEvent) {
					final AUIEvent event = (AUIEvent)arg;
					switch (event.getType()) {
						case DEF_CLOSE_PROGRAM: {
							quit();
							break;
						}
						
						case DEF_CHANGE_UI_STYLE: {
							SwingUtilities.invokeLater(new Runnable() {
								public void run() {
									SwingUtilities.updateComponentTreeUI(UserInterfaceManager.this.frame.getContentPane());
									UserInterfaceManager.this.instruments.updateUIStyle();
								}
							});
							break;
						}
						
						default: break;
					}
				}
			}
		};
		this.addListener(this.eventListener);
		// Run
		this.frame.setVisible(true);
	}
	public void addMenuItem(AMenuItem item, String place) {
		if(item == null || item.getMenuItem() == null) {
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addMenuItem] [FAIL] adding of menu item. Item = null or menuItem = null, place = " + place);
			return;
		}
		MainManager.log.printDebug("[" + this.getClass().getName() + ".addMenuItem] [PROCESS] adding of menu item. Item = " + item.getMenuItem().getText() + ", place = " + place);
		if(place == null) {
			otherMenu.add(item.getMenuItem());
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addMenuItem] [BAD] adding of menu item. Item = " + item.getMenuItem().getText() + ", place = " + place + "(This place is not found.)");
		} else  if(place.equalsIgnoreCase("File")) {
			fileMenu.remove(quitMenuItem);
			fileMenu.remove(importMenu);
			fileMenu.remove(exportMenu);
			fileMenu.add(item.getMenuItem());
			fileMenu.add(importMenu);
			fileMenu.add(exportMenu);
			fileMenu.add(quitMenuItem);
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addMenuItem] [OK] adding of menu item. Item = " + item.getMenuItem().getText() + ", place = " + place);
		} else  if(place.equalsIgnoreCase("Edit")) {
			editMenu.add(item.getMenuItem());
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addMenuItem] [OK] adding of menu item. Item = " + item.getMenuItem().getText() + ", place = " + place);
		} else if(place.equalsIgnoreCase("Window")) {
			windowMenu.add(item.getMenuItem());
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addMenuItem] [OK] adding of menu item. Item = " + item.getMenuItem().getText() + ", place = " + place);
		} else if(place.equalsIgnoreCase("Help")) {
			helpMenu.add(item.getMenuItem());
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addMenuItem] [OK] adding of menu item. Item = " + item.getMenuItem().getText() + ", place = " + place);			
		} else if (place.equalsIgnoreCase("Export")) {
			exportMenu.add(item.getMenuItem());
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addMenuItem] [OK] adding of menu item. Item = " + item.getMenuItem().getText() + ", place = " + place);
		} else if (place.equalsIgnoreCase("Import")) {
			importMenu.add(item.getMenuItem());
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addMenuItem] [OK] adding of menu item. Item = " + item.getMenuItem().getText() + ", place = " + place);
		} else {
			otherMenu.add(item.getMenuItem());
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addMenuItem] [BAD] adding of menu item. Item = " + item.getMenuItem().getText() + ", place = " + place + "(This place is not found.)");
		}
		this.workThread.addElement(item);
	}
	public void addElement(AUserInterfaceElement element, String place) {
		if(element==null) {
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addElement] [FAIL] adding of interface's element. element = null, place = " + place);
			return;
		}
		if(place==null) {
			this.navigatorPanel.addTab(element);
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addElement] [BAD] adding of interface's element. place = " + place + ", name = " + element.getTitleComponent() + "(This place is not found)");
		} else if(place.equalsIgnoreCase("MiniMap")) {
			this.minimapPanel.setMainTab(element);
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addElement] [OK] adding of interface's element. place = " + place + ", name = " + element.getTitleComponent());
		} else if(place.equalsIgnoreCase("Navigator")) {
			this.navigatorPanel.addTab(element);
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addElement] [OK] adding of interface's element. place = " + place + ", name = " + element.getTitleComponent());
		} else if(place.equalsIgnoreCase("Desktop")) {
			this.desktopPanel.setMainTab(element);
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addElement] [OK] adding of interface's element. place = " + place + ", name = " + element.getTitleComponent());
		} else if(place.equalsIgnoreCase("AttributePanel")) {
			this.attributePanel.addTab(element);
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addElement] [OK] adding of interface's element. place = " + place + ", name = " + element.getTitleComponent());
		} else {
			this.navigatorPanel.addTab(element);
			MainManager.log.printDebug("[" + this.getClass().getName() + ".addElement] [BAD] adding of interface's element. place = " + place + ", name = " + element.getTitleComponent());			
		}
		this.workThread.addElement(element);
	}
	public void addListener(Observer obs) {
		this.workThread.addElement(obs);
	}
	public void addInstrument(AUserInterfaceElement element) {
		this.instruments.addInstrument(element.getView());
		this.workThread.addElement(element);
	}
	public void addRequest(AUIRequest request) {
		this.workThread.addRequest(request);
	}
	public void addEvent(AUIEvent event) {
		this.workThread.addEvent(event);
	}
	
	@Override
	public JFrame getMainFrame() {
		return frame;
	}
	
	public void quit() {
		this.model.quit();
		this.frame.dispose();
		this.progressTimer.stop();
		this.workThread.close();
		MainManager.threadPool.shutdown();
		for(Window w : Window.getWindows()) {
			w.dispose();
		}
	}
	/**
	 * Load window options.
	 */
	private void loadWindowOptions() {
		String wSizeStrX = MainManager.config.getProperty(DEF_WINDOWS_SIZE_X);
		String wSizeStrY = MainManager.config.getProperty(DEF_WINDOWS_SIZE_Y);
		String wPosStrX = MainManager.config.getProperty(DEF_WINDOWS_POS_X);
		String wPosStrY = MainManager.config.getProperty(DEF_WINDOWS_POS_Y);
		//set size X(width of window)
		if(wSizeStrX == null) {
			this.windowSizeX = 800;
			MainManager.config.setProperty(DEF_WINDOWS_SIZE_X, this.windowSizeX.toString());
		} else {
			try {
				this.windowSizeX = new Integer(wSizeStrX);
			} catch(NumberFormatException ex) {
				this.windowSizeX = 800;
				MainManager.config.setProperty(DEF_WINDOWS_SIZE_X, this.windowSizeX.toString());
			}
		}
		//set size Y(height of window)
		if(wSizeStrY == null) {
			this.windowSizeY = 600;
			MainManager.config.setProperty(DEF_WINDOWS_SIZE_Y, this.windowSizeY.toString());			
		} else {
			try {
				this.windowSizeY = new Integer(wSizeStrY);
			} catch(NumberFormatException ex) {
				this.windowSizeY = 600;
				MainManager.config.setProperty(DEF_WINDOWS_SIZE_Y, this.windowSizeY.toString());
			}			
		}
		//set position X
		if(wPosStrX == null) {
			this.windowPosX = 200;
			MainManager.config.setProperty(DEF_WINDOWS_POS_X, this.windowPosX.toString());
		} else {
			try {
				this.windowPosX = new Integer(wPosStrX);
			} catch(NumberFormatException ex) {
				this.windowPosX = 800;
				MainManager.config.setProperty(DEF_WINDOWS_POS_X, this.windowPosX.toString());
			}
		}
		//set position Y
		if(wPosStrY == null) {
			this.windowPosY = 200;
			MainManager.config.setProperty(DEF_WINDOWS_POS_Y, this.windowPosY.toString());
		} else {
			try {
				this.windowPosY = new Integer(wPosStrY);
			} catch(NumberFormatException ex) {
				this.windowPosY = 800;
				MainManager.config.setProperty(DEF_WINDOWS_POS_Y, this.windowPosY.toString());
			}
		}
	} 
}
/**
 * This class is main class for user interface. This class provides
 * communications for all modules in the program.
 * @author tzolotuhin
 */
class WorkThread implements Runnable {
	private List<Observer> arrayOfListeners;//array of listeners for different modules(or plugins) in system.
	private List<AUIEvent> arrayOfEvent;//array of events by modules(or plugins).
	private List<AUIRequest> arrayOfRequest;//array of requests by modules(or plugins).
	private boolean close = false;
	//-------------------------------------------------------------------------
	public WorkThread() {
		this.arrayOfListeners = new ArrayList<Observer>();
		this.arrayOfEvent = new ArrayList<AUIEvent>();
		this.arrayOfRequest = new ArrayList<AUIRequest>();
	}
	/**
	 * This method adds new module.
	 */
	public void addElement(Observer e) {
		synchronized (this.arrayOfListeners) {
			this.arrayOfListeners.add(e);		
		}
	}
	/**
	 * This methods adds new event. 
	 * This event will be sent to all system(or user) modules.
	 */
	public void addEvent(AUIEvent event) {
		synchronized (this.arrayOfEvent) {
			this.arrayOfEvent.add(event);
		}
		Thread.interrupted();
	}
	/**
	 * This methods adds new request. 
	 * This request will be sent to all system(or user) modules.
	 */
	public void addRequest(AUIRequest request) {
		synchronized (this.arrayOfRequest) {
			this.arrayOfRequest.add(request);
		}
		Thread.interrupted();
	}
	/**
	 * It's an endless cycle.
	 */
	public void run() {
		//condition of exit 
		while(!this.close) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException ex) {}
			
			int countRequest = 0; // [DEBUG PERFORMANCE]
			int countEvent = 0; // [DEBUG PERFORMANCE]
			
			List<AUIEvent>bufEvents = null;
			List<AUIRequest>bufRequest = null;
			synchronized (this.arrayOfEvent) {
				bufEvents = new ArrayList<AUIEvent>(this.arrayOfEvent);
				countEvent = this.arrayOfEvent.size(); // [DEBUG PERFORMANCE]
				this.arrayOfEvent.clear();
			}
			synchronized (this.arrayOfRequest) {
				bufRequest = new ArrayList<AUIRequest>(this.arrayOfRequest);
				countRequest = this.arrayOfRequest.size(); // [DEBUG PERFORMANCE]
				this.arrayOfRequest.clear();
			}

			long startRun = new Date().getTime(); // [DEBUG PERFORMANCE]

			// circle for events
			for(final AUIEvent bufInner : bufEvents) {
				long startEventTime = new Date().getTime(); // [DEBUG PERFORMANCE]
				synchronized (this.arrayOfListeners) {
					for(final Observer buf : this.arrayOfListeners) {
						try {
							long start = new Date().getTime();
							buf.update(null, bufInner);
							long finish = new Date().getTime();
							if(finish - start > 1) {
								MainManager.log.printDebug("[EVENT] [" + bufInner.getType().toString() + "] [" + buf.getClass().getName() + "] " 
										+ " work time: " + (finish - start) / 1000.0 + " sec");
							}
						} catch (Throwable ex) {
							if(MainManager.log != null) {
								MainManager.log.printException(ex);
							}
							MainManager.windowMessage.errorMessage("Fail.\nException : " + ex.getMessage(), "Thread error");
						}								
					}
				}
				// [DEBUG PERFORMANCE]
				long finishEventTime = new Date().getTime(); 
				if(finishEventTime - startEventTime > 1) {
					MainManager.log.printDebug("[EVENTS FOR ALL PLUGINS] [" + bufInner.getType() + "] work time: " + (finishEventTime - startEventTime) / 1000.0 + " sec");					
				}
			}
			// circle for requests
			for(final AUIRequest bufInner : bufRequest) {
				long startRequestTime = new Date().getTime(); // [DEBUG PERFORMANCE]
				synchronized (this.arrayOfListeners) {
					for(final Observer buf : this.arrayOfListeners) {
						try {
							long start = new Date().getTime();
							buf.update(null, bufInner);
							long finish = new Date().getTime();
							if(finish - start > 1) {
								MainManager.log.printDebug("[REQUEST] [" + bufInner.getType().toString() + "] [" + buf.getClass().getName() + "] " 
										+ " work time: " + (finish - start) / 1000.0 + " sec");
							}
						} catch (Throwable ex) {
							if(MainManager.log != null) {
								MainManager.log.printException(ex);
							}
							MainManager.windowMessage.errorMessage("Fail.\nException : " + ex.getMessage(), "Thread error");
						}
					}
					try {
						IUIRequestOwner owner = bufInner.getOwner();
						if(owner != null) {
							owner.callRequestOwner(new AUIRequest(EUIRequestType.PASS, null){});
						}
					} catch(Throwable ex) {
						MainManager.log.printException(ex);
					}
				}
				// [DEBUG PERFORMANCE]
				long finishRequestTime = new Date().getTime(); 
				if(finishRequestTime - startRequestTime > 1) {
					MainManager.log.printDebug("[REQUESTS FOR ALL PLUGINS] [" + bufInner.getType() + "] work time: " + (finishRequestTime - startRequestTime) / 1000.0 + " sec");					
				}
			}
			// [DEBUG PERFORMANCE]
			long finishRun = new Date().getTime(); 
			if(finishRun - startRun > 10) {
				MainManager.log.printDebug("[WORKTHREAD] Event's count = " + countEvent + " Request's count = " + countRequest + " work time: " + (finishRun - startRun) / 1000.0 + " sec");
			}
		}
	}
	public void close() {
		this.close = true;		
		Thread.interrupted();
	}
}
